//
//  MyOpenAL.m
//  audioTest
//
//  Created by At123456 on 16/4/28.
//  Copyright © 2016年 aite. All rights reserved.
//

#import "ATOpenAL.h"
#import <AVFoundation/AVFoundation.h>
#pragma mark 放大声音

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef unsigned long ULONG_PTR;
typedef ULONG_PTR DWORD_PTR;

#define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))
#define LOWORD(l) ((WORD)((DWORD_PTR)(l) & 0xffff))
#define HIWORD(l) ((WORD)((DWORD_PTR)(l) >> 16))
#define LOBYTE(w) ((BYTE)(WORD)(w))
#define HIBYTE(w) ((BYTE)((WORD)(w) >> 8))


@implementation ATOpenAL
BOOL _isend;
-(BOOL)initOpenAl
{
    _isend = NO;
    if (m_Device ==nil)
    {
        
        
        m_Device = alcOpenDevice(NULL);                      //参数为NULL , 让ALC 使用默认设备
    }
    
    if (m_Device==nil)
    {
        return NO;
    }
    if (m_Context==nil)
    {
        if (m_Device)
        {
            m_Context =alcCreateContext(m_Device, NULL);      //与初始化device是同样的道理
            alcMakeContextCurrent(m_Context);
        }
    }
    //设置听者
    ALCfloat listenerPos[]={0,0,-1};
    ALCfloat listenerVel[]={0,0,0};
    ALCfloat listenerOri[]={0,0,-1,0,1,0};
    alListenerfv(AL_POSITION,listenerPos);
    alListenerfv(AL_VELOCITY,listenerVel);
    alListenerfv(AL_ORIENTATION,listenerOri);
    
    alGenSources(1, &m_sourceID);                                                           //初始化音源ID
    alSourcei(m_sourceID, AL_LOOPING, AL_FALSE);                         // 设置音频播放是否为循环播放，AL_FALSE是不循环
    alSourcef(m_sourceID, AL_SOURCE_TYPE, AL_STREAMING);  // 设置声音数据为流试，（openAL 针对PCM格式数据流）
    alSourcef(m_sourceID, AL_GAIN, 1.0f);                                               //设置音量大小，1.0f表示最大音量。openAL动态调节音量大小就用这个方法
    //    alDopplerVelocity(1.0);                                                                         //多普勒效应，这属于高级范畴，不是做游戏开发，对音质没有苛刻要求的话，一般无需设置
    //    alDopplerFactor(1.0);                                                                            //同上
    alSpeedOfSound(1.0);                                                                            //设置声音的播放速度
    
    m_DecodeLock =[[NSCondition alloc] init];
    if (m_Context==nil)
    {
        return NO;
    }
       /*这里有我注释掉的监测方法，alGetError()用来监测环境搭建过程中是否有错误
     在这里，可以说是是否出错都可以，为什么这样说呢？ 因为运行到这里之前，
     如果加上了alSourcef(m_sourceID, AL_SOURCE_TYPE, AL_STREAMING);
     这个方法，这里就会监测到错误，注释掉这个方法就不会有错误。（具体为什么，我
     也不知道～～～，知道的大神麻烦说下～～～），加上这个方法，在这里监测出错误
     对之后播放声音无影响，所以，这里可以注释掉下面的alGetError()。
     */
    //    ALenum  error;
    //    if ((error=alGetError())!=AL_NO_ERROR)
    //    {
    //        return NO;
    //    }
    return YES;
}
ALint  state;
int processed ,queued;
//清楚已存在的buffer，这个函数其实没什么的，就只是用来清空缓存而已，我只是多一步将播放声音放到这个函数里。
-(BOOL)updataQueueBuffer
{
    
    
    alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
    if (state !=AL_PLAYING)
    {
        [self playSound];
        return NO;
    }
    
    alGetSourcei(m_sourceID, AL_BUFFERS_PROCESSED, &processed);
    alGetSourcei(m_sourceID, AL_BUFFERS_QUEUED, &queued);
    
    
//    NSLog(@"Processed = %d\n", processed);
//    NSLog(@"Queued = %d\n", queued);
    while (processed>0)
    {
        ALuint  buffer;
        alSourceUnqueueBuffers(m_sourceID, 1, &buffer);
        alDeleteBuffers(1, &buffer);
        processed-=1;
    }
    return YES;
}

//这个函数就是比较重要的函数了， 将收到的pcm数据放到缓存器中，再拿出来播放
-(void)openAudio:(unsigned char*)pBuffer length:(UInt32)pLength
{
    
    [m_DecodeLock lock];
    
    ALenum  error =AL_NO_ERROR;
    if ((error =alGetError())!=AL_NO_ERROR)
    {
        [m_DecodeLock unlock];
        return ;
    }
    if (pBuffer ==NULL)
    {
        return ;
    }
    
    [self updataQueueBuffer];                                  //在这里调用了刚才说的清除缓存buffer函数，也附加声音播放
    
    if ((error =alGetError())!=AL_NO_ERROR)
    {
        [m_DecodeLock unlock];
        return ;
    }
    
    ALuint    bufferID =0;                                             //存储声音数据，建立一个pcm数据存储器，初始化一块区域用来保存声音数据
    alGenBuffers(1, &bufferID);
    
    if ((error = alGetError())!=AL_NO_ERROR)
    {
        NSLog(@"Create buffer failed");
        [m_DecodeLock unlock];
        return;
    }
    
    NSData  *data =[NSData dataWithBytes:pBuffer length:pLength];                                                                    //将PCM格式数据转换成NSData ,
    alBufferData(bufferID, AL_FORMAT_MONO16, (char *)[data bytes] , (ALsizei)[data length], 8000 );         //将转好的NSData存放到之前初始化好的一块buffer区域中并设置好相应的播放格式 ，（本人使用的播放格式: 单声道16bit(AL_FORMAT_MONO16) , 采样率 8000HZ）
    
    if ((error =alGetError())!=AL_NO_ERROR)
    {
        NSLog(@"create bufferData failed");
        [m_DecodeLock unlock];
        return;
    }
    
    //添加到缓冲区
    alSourceQueueBuffers(m_sourceID, 1, &bufferID);
    
    if ((error =alGetError())!=AL_NO_ERROR)
    {
        NSLog(@"add buffer to queue failed");
        [m_DecodeLock unlock];
        return;
    }
    if ((error=alGetError())!=AL_NO_ERROR)
    {
        NSLog(@"play failed");
        alDeleteBuffers(1, &bufferID);
        [m_DecodeLock unlock];
        return;
    }
    
    [m_DecodeLock unlock];
    
}
-(void)playSound
{
    ALint  state;
    alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
    if (state != AL_PLAYING)
    {
        alSourcePlay(m_sourceID);
    }
}

-(void)stopSound
{
    ALint  state;
    alGetSourcei(m_sourceID, AL_SOURCE_STATE, &state);
    if (state != AL_STOPPED)
    {
        
        alSourceStop(m_sourceID);
    }
//    [self clearOpenAL];
}

-(void)clearOpenAL
{
    _isend = YES;
//    while (queued>0) {
//        queued--;
//        ALuint  buffer;
//        alSourceUnqueueBuffers(m_sourceID, 1, &buffer);
//        alDeleteBuffers(1, &buffer);
//    }
    alDeleteSources(1, &m_sourceID);
    if (m_Context != nil)
    {
        alcDestroyContext(m_Context);
        m_Context=nil;
    }
    if (m_Device !=nil)
    {
        alcCloseDevice(m_Device);
        m_Device=nil; 
    } 
}
//扬声器
- (void) setSpeaker:(BOOL)speaker{
    UInt32 audioRouteOverride = speaker ? kAudioSessionOverrideAudioRoute_Speaker:kAudioSessionOverrideAudioRoute_None;
    AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, sizeof(audioRouteOverride), &audioRouteOverride);
    
    
}
- (void) RaiseVolume:(char*) buf size:(int) size urepeat:(int) uRepeat vol:(double) vol
{
    int i=0;
    if (!size)
    {
        return;
    }
    for (i=0;i<size;)
    {
        //        signed long minData = -0x8000;
        //        signed long maxData = 0x7FFF;
        signed short wData = buf[i+1];
        wData = MAKEWORD(buf[i],buf[i+1]);
        signed long dwData = wData;
        
        for (int j = 0; j < uRepeat; j++)
        {
            dwData = dwData * vol;
            if (dwData < -0x8000)
            {
                dwData = -0x8000;
            }
            else if (dwData > 0x7FFF)
            {
                dwData = 0x7FFF;
            }
        }
        wData = LOWORD(dwData);
        buf[i] = LOBYTE(wData);
        buf[i+1] = HIBYTE(wData);
        i += 2;
    }
}
/*
 一、方法alSourcePlay(m_sourceID);
 很多网上的例子都在问为什么程序运行的时候一直听不到声音或者声音一直卡着，其实是在调用alSourcePlay(m_sourceID); 方法
 之前，没有对整个环境进行一个判断，就是没有判断当前播放器是否已经是播放状态了(AL_PLAYING) ,所以每次来一帧数据，都会
 调用一次开启播放方法，这样，声音就会卡着。这一点，算不上是难点，但是比较容易忽略，相信很多用openAL的朋友都会注意到
 这个问题。
 二、其二就是，很多人在网上发帖问为什么播放不了声音或者是有杂音，杂音很大，其实这个问题跟这个方法 alBufferData 有直接的关系，
 能否播放出正常的声音，全看你的参数怎么给了。 我相信很多人在调用 alBufferData这个方法的时候，对于里面的参数很迷惑，不知道
 该怎么填，我看的最多的参数就是 alBufferData(bufferID,AL_FORMAT_STETERO16,(char *)[data bytes] ,(ALsizei)[data length], 44100),
 我也相信，很多人对参数AL_FORMAT_STETERO16，和44100不解，不知道为什么这么填，而且根据网上也是这样填的，怎么自己就
 是播放不出来声音或者， 很大杂音之类的。这里我就简单的说一下，AL_FORMAT_STETERO16  表示的是双声道 16bit, 44100表示的是
 音频采样率。PCM格式的数据，一般有单声道16bit（AL_FORMAT_MONO16），采样率为22050 和双声道16bit,采样率为44100. 不过
 也有单声道8bit.和双声道8bit。  所以这就不难看出为什么网上别人的参数为什么会这样写了。
 但是，还有一点，也就是为什么你的声音出不来的重要一点，就是，你的数据或者你的设备，不适合网上的AL_FORMAT_STETERO16，
 44100。你需要自己来为自己的设备匹配适合的参数，这也是最难 部分，自己的设备，哪一套参数才适合呢，这个，我也说不准。我记得
 在我原来公司用openAL的时候，我用的貌似是一套AL_FORMAT_MONO8，8000。 现在新公司用的是AL_FORMAT_MONO16，8000.
 openAL  提供了4种声道选择，AL_FORMAT_MONO8，AL_FORMAT_MONO16，AL_FORMAT_STETERO8，AL_FORMAT_STETERO16，
 对于采样率的选择却没有明确给出，一般是不大于人类的识别频率就行(<48000)。 而常用的采样率也就是8000，22050，44100 这三种了，
 当然也有些奇葩的值，这个需要你自己来定了。
 
 说了这么多，其实重要的也就下面的一条而已，难也只是难在这里，因为我个人也在网上找了好多例子，但是就是没有人讲声道跟采样率的适配，
 我就来这里多嘴一句，由于很少发帖， 所以文笔不好，各位不要介意，个人觉得应该能看得懂了。  再有如果其他地方的原因，应该是传入的数据
 的问题了， 可能你装pcm数据的数组大于你实际pcm数据长度，这样可能会导致播放器停止播放，一般做法是初始化数组的时候也将整个数组填充
 有效字符，避免播放器自动停止播放的情况。
 */
@end
